🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / messaging / src / commonMain / kotlin / org / meshtastic / feature / messaging / MessageFormatting.kt
Displaying Raw • Download
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageFormatting.kt bc5cf83df7fd6b405ffc521d7c3ed4e1f3e8271a (bc5cf83d) Text, 8.42 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.messaging
Tff7b72import T7ee787androidx.compose.ui.text.TextRange
Tff7b72import T7ee787org.meshtastic.core.ui.component.InlineStyle
T8b949e// Pure text-mutation helpers backing the formatting toolbar, mirroring the iOS client's MarkdownFormatting.swift so
T8b949e// the two platforms produce byte-for-byte identical markup for the same selection + action. No UI-framework
T8b949e// dependency beyond the multiplatform `TextRange` value type, so these run in commonTest.
T8b949e/**
* Toggles [style] markup around the selection [range] of [text]: wraps an unwrapped selection, removes the delimiters
* from an already-wrapped one, and inserts an empty delimiter pair for a collapsed cursor. Whitespace is kept outside
* the delimiters, delimiter characters the selection cuts through are absorbed, and orphaned delimiters left elsewhere
* are cleaned up.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4) T8b949e// Guard clauses (toggle-off, empty-selection) read more clearly than one nested expression.
Tff7b72fun Td2a8ffwrapSelectionTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3rangeTb4b4b4: Te6edf3TextRangeTb4b4b4, Te6edf3styleTb4b4b4: Te6edf3InlineStyleTb4b4b4)Tb4b4b4: Te6edf3FormattingResult Tb4b4b4{
Tff7b72val Te6edf3opening Tff7b72= Te6edf3styleTb4b4b4.Te6edf3openingDelimiterTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3closing Tff7b72= Te6edf3styleTb4b4b4.Te6edf3closingDelimiterTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3start Tff7b72= Te6edf3rangeTb4b4b4.Te6edf3min
Tff7b72val Te6edf3end Tff7b72= Te6edf3rangeTb4b4b4.Te6edf3max
Tff7b72val Te6edf3hasOpeningBefore Tff7b72= Te6edf3start Tff7b72- Te6edf3openingTb4b4b4.Te6edf3length Tff7b72>Tff7b72= T79c0ff0 Tff7b72&Tff7b72& Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3start Tff7b72- Te6edf3openingTb4b4b4.Te6edf3lengthTb4b4b4, Te6edf3startTb4b4b4) Tff7b72=Tff7b72= Te6edf3opening
Tff7b72val Te6edf3hasClosingAfter Tff7b72= Te6edf3end Tff7b72+ Te6edf3closingTb4b4b4.Te6edf3length Tff7b72<Tff7b72= Te6edf3textTb4b4b4.Te6edf3length Tff7b72&Tff7b72& Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3endTb4b4b4, Te6edf3end Tff7b72+ Te6edf3closingTb4b4b4.Te6edf3lengthTb4b4b4) Tff7b72=Tff7b72= Te6edf3closing
Tff7b72if Tb4b4b4(Te6edf3hasOpeningBefore Tff7b72&Tff7b72& Te6edf3hasClosingAfterTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3delimiterStart Tff7b72= Te6edf3start Tff7b72- Te6edf3openingTb4b4b4.Te6edf3length
Tff7b72val Te6edf3delimiterEnd Tff7b72= Te6edf3end Tff7b72+ Te6edf3closingTb4b4b4.Te6edf3length
Tff7b72val Te6edf3newText Tff7b72= Te6edf3buildString Tb4b4b4{
Te6edf3appendTb4b4b4(Te6edf3textTb4b4b4, T79c0ff0Tb4b4b4, Te6edf3delimiterStartTb4b4b4)
Te6edf3appendTb4b4b4(Te6edf3textTb4b4b4, Te6edf3startTb4b4b4, Te6edf3endTb4b4b4)
Te6edf3appendTb4b4b4(Te6edf3textTb4b4b4, Te6edf3delimiterEndTb4b4b4, Te6edf3textTb4b4b4.Te6edf3lengthTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3resultStart Tff7b72= Te6edf3delimiterStart
Tff7b72return Te6edf3FormattingResultTb4b4b4(Te6edf3newTextTb4b4b4, Te6edf3TextRangeTb4b4b4(Te6edf3resultStartTb4b4b4, Te6edf3resultStart Tff7b72+ Tb4b4b4(Te6edf3end Tff7b72- Te6edf3startTb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72val Tb4b4b4(Te6edf3expandedStartTb4b4b4, Te6edf3expandedEndTb4b4b4) Tff7b72= Te6edf3expandToDelimiterBoundariesTb4b4b4(Te6edf3textTb4b4b4, Te6edf3startTb4b4b4, Te6edf3endTb4b4b4)
Tff7b72val Te6edf3cleaned Tff7b72= Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3expandedStartTb4b4b4, Te6edf3expandedEndTb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657it Tff7b72!in Te6edf3DELIMITER_CHARS Tb4b4b4}
Tff7b72val Te6edf3firstNonWs Tff7b72= Te6edf3cleanedTb4b4b4.Te6edf3indexOfFirst Tb4b4b4{ Tff7b72!Tffa657itTb4b4b4.Te6edf3isWhitespaceTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3firstNonWs Tff7b72< T79c0ff0Tb4b4b4) Tff7b72return Te6edf3insertDelimitersTb4b4b4(Te6edf3textTb4b4b4, Te6edf3expandedStartTb4b4b4, Te6edf3styleTb4b4b4)
Tff7b72val Te6edf3lastNonWs Tff7b72= Te6edf3cleanedTb4b4b4.Te6edf3indexOfLast Tb4b4b4{ Tff7b72!Tffa657itTb4b4b4.Te6edf3isWhitespaceTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3leadingWs Tff7b72= Te6edf3cleanedTb4b4b4.Te6edf3substringTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3firstNonWsTb4b4b4)
Tff7b72val Te6edf3trailingWs Tff7b72= Te6edf3cleanedTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3lastNonWs Tff7b72+ T79c0ff1Tb4b4b4)
Tff7b72val Te6edf3trimmed Tff7b72= Te6edf3cleanedTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3firstNonWsTb4b4b4, Te6edf3lastNonWs Tff7b72+ T79c0ff1Tb4b4b4)
Tff7b72val Te6edf3wrapped Tff7b72= Te6edf3leadingWs Tff7b72+ Te6edf3opening Tff7b72+ Te6edf3trimmed Tff7b72+ Te6edf3closing Tff7b72+ Te6edf3trailingWs
Tff7b72val Te6edf3newText Tff7b72=
Tb4b4b4(Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3expandedStartTb4b4b4) Tff7b72+ Te6edf3wrapped Tff7b72+ Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3expandedEndTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3letTb4b4b4(Tff7b72::Te6edf3cleanOrphanedDelimitersTb4b4b4)
Tff7b72val Te6edf3fullWrapped Tff7b72= Te6edf3opening Tff7b72+ Te6edf3trimmed Tff7b72+ Te6edf3closing
Tff7b72val Te6edf3found Tff7b72= Te6edf3newTextTb4b4b4.Te6edf3indexOfTb4b4b4(Te6edf3fullWrappedTb4b4b4)
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3found Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3FormattingResultTb4b4b4(Te6edf3newTextTb4b4b4, Te6edf3TextRangeTb4b4b4(Te6edf3foundTb4b4b4, Te6edf3found Tff7b72+ Te6edf3fullWrappedTb4b4b4.Te6edf3lengthTb4b4b4)Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72val Te6edf3selStart Tff7b72= Tb4b4b4(Te6edf3expandedStart Tff7b72+ Te6edf3leadingWsTb4b4b4.Te6edf3lengthTb4b4b4)Tb4b4b4.Te6edf3coerceInTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3newTextTb4b4b4.Te6edf3lengthTb4b4b4)
Tff7b72val Te6edf3selEnd Tff7b72= Tb4b4b4(Te6edf3selStart Tff7b72+ Te6edf3fullWrappedTb4b4b4.Te6edf3lengthTb4b4b4)Tb4b4b4.Te6edf3coerceInTb4b4b4(Te6edf3selStartTb4b4b4, Te6edf3newTextTb4b4b4.Te6edf3lengthTb4b4b4)
Te6edf3FormattingResultTb4b4b4(Te6edf3newTextTb4b4b4, Te6edf3TextRangeTb4b4b4(Te6edf3selStartTb4b4b4, Te6edf3selEndTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/** Inserts an empty [style] delimiter pair at [index], returning a collapsed cursor between the delimiters. */
Tff7b72fun Td2a8ffinsertDelimitersTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3indexTb4b4b4: Tffa657IntTb4b4b4, Te6edf3styleTb4b4b4: Te6edf3InlineStyleTb4b4b4)Tb4b4b4: Te6edf3FormattingResult Tb4b4b4{
Tff7b72val Te6edf3opening Tff7b72= Te6edf3styleTb4b4b4.Te6edf3openingDelimiterTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3closing Tff7b72= Te6edf3styleTb4b4b4.Te6edf3closingDelimiterTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3newText Tff7b72= Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3indexTb4b4b4) Tff7b72+ Te6edf3opening Tff7b72+ Te6edf3closing Tff7b72+ Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3indexTb4b4b4)
Tff7b72val Te6edf3cursor Tff7b72= Te6edf3index Tff7b72+ Te6edf3openingTb4b4b4.Te6edf3length
Tff7b72return Te6edf3FormattingResultTb4b4b4(Te6edf3newTextTb4b4b4, Te6edf3TextRangeTb4b4b4(Te6edf3cursorTb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/** Wraps the selection as `[selection](url)`, or inserts a `[link text](url)` placeholder for a collapsed cursor. */
Tff7b72fun Td2a8ffwrapSelectionWithLinkTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3rangeTb4b4b4: Te6edf3TextRangeTb4b4b4, Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3FormattingResult Tb4b4b4{
Tff7b72val Te6edf3start Tff7b72= Te6edf3rangeTb4b4b4.Te6edf3min
Tff7b72val Te6edf3end Tff7b72= Te6edf3rangeTb4b4b4.Te6edf3max
Tff7b72val Te6edf3selected Tff7b72= Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3startTb4b4b4, Te6edf3endTb4b4b4)
Tff7b72val Te6edf3replacement Tff7b72= Tff7b72if Tb4b4b4(Te6edf3selectedTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Ta5d6ff"Ta5d6ff[link text](Tffd700$Te6edf3urlTa5d6ff)Ta5d6ff" Tff7b72else Ta5d6ff"Ta5d6ff[Tffd700$Te6edf3selectedTa5d6ff](Tffd700$Te6edf3urlTa5d6ff)Ta5d6ff"
Tff7b72val Te6edf3newText Tff7b72= Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3startTb4b4b4) Tff7b72+ Te6edf3replacement Tff7b72+ Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3endTb4b4b4)
Tff7b72return Te6edf3FormattingResultTb4b4b4(Te6edf3newTextTb4b4b4, Te6edf3TextRangeTb4b4b4(Te6edf3startTb4b4b4, Te6edf3start Tff7b72+ Te6edf3replacementTb4b4b4.Te6edf3lengthTb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/** Unwraps a `[label](url)` selection back to its label, or returns null when the selection is not a markdown link. */
Tff7b72fun Td2a8ffunwrapLinkTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3rangeTb4b4b4: Te6edf3TextRangeTb4b4b4)Tb4b4b4: Te6edf3FormattingResult? Tb4b4b4{
Tff7b72val Te6edf3start Tff7b72= Te6edf3rangeTb4b4b4.Te6edf3min
Tff7b72val Te6edf3end Tff7b72= Te6edf3rangeTb4b4b4.Te6edf3max
Tff7b72val Te6edf3match Tff7b72= Te6edf3LINK_PATTERNTb4b4b4.Te6edf3findTb4b4b4(Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3startTb4b4b4, Te6edf3endTb4b4b4)Tb4b4b4) Tff7b72?: Tff7b72return Tff7b72null
Tff7b72val Te6edf3label Tff7b72= Te6edf3matchTb4b4b4.Te6edf3groupValuesTff7b72[T79c0ff1Tff7b72]
Tff7b72val Te6edf3newText Tff7b72= Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3startTb4b4b4) Tff7b72+ Te6edf3label Tff7b72+ Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3endTb4b4b4)
Tff7b72return Te6edf3FormattingResultTb4b4b4(Te6edf3newTextTb4b4b4, Te6edf3TextRangeTb4b4b4(Te6edf3startTb4b4b4, Te6edf3start Tff7b72+ Te6edf3labelTb4b4b4.Te6edf3lengthTb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/** True when [text] is exactly a `[label](url)` markdown link. */
Tff7b72fun Td2a8ffisMarkdownLinkTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3LINK_PATTERNTb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3textTb4b4b4)
T8b949e/** True when [text] contains any recognised paired inline-markdown syntax. */
Tff7b72fun Td2a8ffcontainsMarkdownSyntaxTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3textTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Tff7b72false
T8b949e// Italic strips `**` first so a bold run isn't mistaken for italic (avoids KMP-unsafe lookbehind).
Tff7b72return Te6edf3BOLD_PATTERNTb4b4b4.Te6edf3containsMatchInTb4b4b4(Te6edf3textTb4b4b4) Tff7b72|Tff7b72|
Te6edf3ITALIC_PATTERNTb4b4b4.Te6edf3containsMatchInTb4b4b4(Te6edf3textTb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff"Ta5d6ff**Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4) Tff7b72|Tff7b72|
Te6edf3STRIKE_PATTERNTb4b4b4.Te6edf3containsMatchInTb4b4b4(Te6edf3textTb4b4b4) Tff7b72|Tff7b72|
Te6edf3CODE_PATTERNTb4b4b4.Te6edf3containsMatchInTb4b4b4(Te6edf3textTb4b4b4) Tff7b72|Tff7b72|
Te6edf3INLINE_LINK_PATTERNTb4b4b4.Te6edf3containsMatchInTb4b4b4(Te6edf3textTb4b4b4)
Tb4b4b4}
T8b949e/** New raw text plus the selection to apply to the field after a formatting action. */
Tff7b72data Tff7b72class T56d364FormattingResultTb4b4b4(Tff7b72val Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Tff7b72val Te6edf3selectionTb4b4b4: Te6edf3TextRangeTb4b4b4)
Tff7b72private Tff7b72val Te6edf3DELIMITER_CHARS Tff7b72= Te6edf3setOfTb4b4b4(Ta5d6ff'*'Tb4b4b4, Ta5d6ff'~'Tb4b4b4, Ta5d6ff'`'Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3LINK_PATTERN Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff^Ta5d6ff\\Ta5d6ff[([^Ta5d6ff\\Ta5d6ff]]+)Ta5d6ff\\Ta5d6ff]Ta5d6ff\\Ta5d6ff(([^)]+)Ta5d6ff\\Ta5d6ff)$Ta5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3BOLD_PATTERN Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff\\Ta5d6ff*Ta5d6ff\\Ta5d6ff*[^*]+Ta5d6ff\\Ta5d6ff*Ta5d6ff\\Ta5d6ff*Ta5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3ITALIC_PATTERN Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff\\Ta5d6ff*[^*]+Ta5d6ff\\Ta5d6ff*Ta5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3STRIKE_PATTERN Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff~~[^~]+~~Ta5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3CODE_PATTERN Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff`[^`]+`Ta5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3INLINE_LINK_PATTERN Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff\\Ta5d6ff[[^Ta5d6ff\\Ta5d6ff]]+Ta5d6ff\\Ta5d6ff]Ta5d6ff\\Ta5d6ff([^)]+Ta5d6ff\\Ta5d6ff)Ta5d6ff"Tb4b4b4)
Tff7b72private Tff7b72fun Te6edf3InlineStyleTb4b4b4.Td2a8ffopeningDelimiterTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Tff7b72when Tb4b4b4(Tff7b72thisTb4b4b4) Tb4b4b4{
Te6edf3InlineStyleTb4b4b4.Te6edf3Bold Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff**Ta5d6ff"
Te6edf3InlineStyleTb4b4b4.Te6edf3Italic Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff*Ta5d6ff"
Te6edf3InlineStyleTb4b4b4.Te6edf3Strikethrough Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff~~Ta5d6ff"
Te6edf3InlineStyleTb4b4b4.Te6edf3Code Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff`Ta5d6ff"
Te6edf3InlineStyleTb4b4b4.Te6edf3Link Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ff[Ta5d6ff"
Tb4b4b4}
Tff7b72private Tff7b72fun Te6edf3InlineStyleTb4b4b4.Td2a8ffclosingDelimiterTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Tff7b72if Tb4b4b4(Tff7b72this Tff7b72=Tff7b72= Te6edf3InlineStyleTb4b4b4.Te6edf3LinkTb4b4b4) Ta5d6ff"Ta5d6ff]Ta5d6ff" Tff7b72else Te6edf3openingDelimiterTb4b4b4(Tb4b4b4)
T8b949e/**
* Expands [start]..[end] outward across any contiguous delimiter characters they touch, so wrapping never leaves a
* half-delimiter behind.
*/
Tff7b72private Tff7b72fun Td2a8ffexpandToDelimiterBoundariesTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3startTb4b4b4: Tffa657IntTb4b4b4, Te6edf3endTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3PairTff7b72<Tffa657IntTb4b4b4, Tffa657IntTff7b72> Tb4b4b4{
Tff7b72val Te6edf3insideHasDelimiter Tff7b72= Te6edf3textTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3startTb4b4b4, Te6edf3endTb4b4b4)Tb4b4b4.Te6edf3any Tb4b4b4{ Tffa657it Tff7b72in Te6edf3DELIMITER_CHARS Tb4b4b4}
Tff7b72val Te6edf3beforeIsDelimiter Tff7b72= Te6edf3start Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3textTff7b72[Te6edf3start Tff7b72- T79c0ff1Tff7b72] Tff7b72in Te6edf3DELIMITER_CHARS
Tff7b72val Te6edf3afterIsDelimiter Tff7b72= Te6edf3end Tff7b72< Te6edf3textTb4b4b4.Te6edf3length Tff7b72&Tff7b72& Te6edf3textTff7b72[Te6edf3endTff7b72] Tff7b72in Te6edf3DELIMITER_CHARS
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3insideHasDelimiter Tff7b72&Tff7b72& Tff7b72!Te6edf3beforeIsDelimiter Tff7b72&Tff7b72& Tff7b72!Te6edf3afterIsDelimiterTb4b4b4) Tff7b72return Te6edf3start Te6edf3to Te6edf3end
Tff7b72var Te6edf3lower Tff7b72= Te6edf3start
Tff7b72while Tb4b4b4(Te6edf3lower Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3textTff7b72[Te6edf3lower Tff7b72- T79c0ff1Tff7b72] Tff7b72in Te6edf3DELIMITER_CHARSTb4b4b4) Te6edf3lowerTff7b72-Tff7b72-
Tff7b72var Te6edf3upper Tff7b72= Te6edf3end
Tff7b72while Tb4b4b4(Te6edf3upper Tff7b72< Te6edf3textTb4b4b4.Te6edf3length Tff7b72&Tff7b72& Te6edf3textTff7b72[Te6edf3upperTff7b72] Tff7b72in Te6edf3DELIMITER_CHARSTb4b4b4) Te6edf3upperTff7b72+Tff7b72+
Tff7b72return Te6edf3lower Te6edf3to Te6edf3upper
Tb4b4b4}
T8b949e/** Removes unpaired (odd-count) delimiter runs, preserving properly paired `**`, `~~`, `` ` `` and `*`. */
Tff7b72private Tff7b72fun Td2a8ffcleanOrphanedDelimitersTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String Tb4b4b4{
Tff7b72var Te6edf3result Tff7b72= Te6edf3text
Tff7b72for Tb4b4b4(Te6edf3delimiter Tff7b72in Te6edf3listOfTb4b4b4(Ta5d6ff"Ta5d6ff**Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff~~Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff`Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff*Ta5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3result Tff7b72= Te6edf3cleanOrphanedPairsTb4b4b4(Te6edf3resultTb4b4b4, Te6edf3delimiterTb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3result
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffcleanOrphanedPairsTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3delimiterTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String Tb4b4b4{
Tff7b72var Te6edf3count Tff7b72= T79c0ff0
Tff7b72var Te6edf3index Tff7b72= Te6edf3textTb4b4b4.Te6edf3indexOfTb4b4b4(Te6edf3delimiterTb4b4b4)
Tff7b72while Tb4b4b4(Te6edf3index Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3countTff7b72+Tff7b72+
Te6edf3index Tff7b72= Te6edf3textTb4b4b4.Te6edf3indexOfTb4b4b4(Te6edf3delimiterTb4b4b4, Te6edf3index Tff7b72+ Te6edf3delimiterTb4b4b4.Te6edf3lengthTb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3count Tff7b72% T79c0ff2 Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tff7b72return Te6edf3text
Tff7b72val Te6edf3last Tff7b72= Te6edf3textTb4b4b4.Te6edf3lastIndexOfTb4b4b4(Te6edf3delimiterTb4b4b4)
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3last Tff7b72< T79c0ff0Tb4b4b4) Te6edf3text Tff7b72else Te6edf3textTb4b4b4.Te6edf3removeRangeTb4b4b4(Te6edf3lastTb4b4b4, Te6edf3last Tff7b72+ Te6edf3delimiterTb4b4b4.Te6edf3lengthTb4b4b4)
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.1s